﻿// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace umbraco.ACSExtensions
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Xml.Linq;
    using umbraco.IO;

    public class XmlMappingRuleRepository : IMappingRuleRepository
    {
        private readonly string filePath;

        public XmlMappingRuleRepository()
            : this(ACSExtensionsSettings.IdentityProvidersMappingRulesPath)
        {
        }

        public XmlMappingRuleRepository(string filePath)
        {
            this.filePath = IOHelper.MapPath(ACSExtensionsSettings.IdentityProvidersMappingRulesPath, false);
        }

        public MappingRule RetrieveMappingRule(Guid id)
        {
            var mappingRules = this.LoadXml();
            MappingRule rule = null;

            if (mappingRules != null)
            {
                rule = (from r in mappingRules.Descendants("mappingRule")
                        where r.Attribute("id").Value.Equals(id.ToString(), StringComparison.OrdinalIgnoreCase)
                        select
                            new MappingRule
                            {
                                Id = new Guid(r.Attribute("id").Value),
                                Name = r.Attribute("name").Value,
                                ClaimType = r.Attribute("claimType").Value,
                                ClaimValue = r.Attribute("claimValue").Value,
                                MemberGroup = new MemberGroupInfo 
                                {
                                    Id = int.Parse(r.Parent.Parent.Attribute("id").Value, CultureInfo.InvariantCulture),
                                    Name = r.Parent.Parent.Attribute("name").Value
                                },
                                IdentityProvider = new IdentityProviderInfo
                                {
                                    Id = r.Parent.Parent.Parent.Attribute("id").Value,
                                    Name = r.Parent.Parent.Parent.Attribute("name").Value
                                }
                            }).SingleOrDefault();
            }

            return rule;
        }

        public IEnumerable<IdentityProviderInfo> RetrieveIdentityProviders()
        {
            var mappingRules = this.LoadXml();
            var identityProviders = new List<IdentityProviderInfo>();

            if (mappingRules != null)
            {
                identityProviders = (from r in mappingRules.Descendants("identityProvider")
                                     select
                                         new IdentityProviderInfo
                                        {
                                            Id = r.Attribute("id").Value,
                                            Name = r.Attribute("name").Value
                                        }).ToList();
            }

            return identityProviders;
        }

        public IEnumerable<MappingRule> RetrieveMappingRulesByMemberGroup(IdentityProviderInfo identityProvider, MemberGroupInfo memberGroup)
        {
            var mappingRules = this.LoadXml();
            var rules = new List<MappingRule>();

            if (mappingRules != null)
            {
                rules = (from r in mappingRules.Descendants("mappingRule")
                         where
                             r.Parent.Parent.Parent.Attribute("id").Value.Equals(identityProvider.Id, StringComparison.OrdinalIgnoreCase) &&
                             r.Parent.Parent.Attribute("id").Value == memberGroup.Id.ToString()
                         select
                             new MappingRule
                             {
                                 Id = new Guid(r.Attribute("id").Value),
                                 Name = r.Attribute("name").Value,
                                 ClaimType = r.Attribute("claimType").Value,
                                 ClaimValue = r.Attribute("claimValue").Value,
                                 MemberGroup = new MemberGroupInfo
                                 {
                                     Id = int.Parse(r.Parent.Parent.Attribute("id").Value, CultureInfo.InvariantCulture),
                                     Name = r.Parent.Parent.Attribute("name").Value
                                 },
                                 IdentityProvider = new IdentityProviderInfo
                                 {
                                     Id = r.Parent.Parent.Parent.Attribute("id").Value,
                                     Name = r.Parent.Parent.Parent.Attribute("name").Value
                                 }
                             }).ToList();

                if (rules.Count == 0)
                {
                    var identityProviders = EnsureIdentityProviderSection(identityProvider, ref mappingRules);
                    EnsureMemberGroupSection(identityProvider, memberGroup, ref mappingRules, identityProviders);
                    
                    mappingRules.Save(this.filePath);
                }
            }

            return rules;
        }

        public void AddMappingRule(MappingRule mappingRule)
        {
            var currentMappingRule = this.RetrieveMappingRule(mappingRule.Id);
            if (currentMappingRule != null && currentMappingRule.Id != Guid.Empty)
            {
                this.EditMappingRule(mappingRule);
            }
            else
            {
                var mappingRules = this.LoadXml();
                if (mappingRules != null)
                {
                    var identityProviders = EnsureIdentityProviderSection(mappingRule.IdentityProvider, ref mappingRules);
                    var memberGroups = EnsureMemberGroupSection(mappingRule.IdentityProvider, mappingRule.MemberGroup, ref mappingRules, identityProviders);

                    // Ensure OR node
                    var orNode = memberGroups.Single(mg => mg.Attribute("id").Value == mappingRule.MemberGroup.Id.ToString()).Element("or");
                    if (orNode == null)
                    {
                        var xml = new XElement("or");
                        memberGroups.Single(mg => mg.Attribute("id").Value == mappingRule.MemberGroup.Id.ToString()).Add(xml);
                        orNode = memberGroups.Single(mg => mg.Attribute("id").Value == mappingRule.MemberGroup.Id.ToString()).Element("or");
                    }

                    var xmlRule = new XElement("mappingRule",
                                    new XAttribute("id", mappingRule.Id),
                                    new XAttribute("name", mappingRule.Name),
                                    new XAttribute("claimType", mappingRule.ClaimType),
                                    new XAttribute("claimValue", mappingRule.ClaimValue));

                    var groupMappingRules = orNode.Elements("mappingRule");
                    if (groupMappingRules.Count() == 0)
                    {
                        orNode.Add(xmlRule);
                    }
                    else
                    {
                        groupMappingRules.Last().AddAfterSelf(xmlRule);
                    }

                    mappingRules.Save(this.filePath);
                }
            }
        }

        public void EditMappingRule(MappingRule mappingRule)
        {
            var mappingRules = this.LoadXml();

            if (mappingRules != null)
            {
                var rule = (from r in mappingRules.Descendants("mappingRule")
                            where r.Attribute("id").Value.Equals(mappingRule.Id.ToString(), StringComparison.OrdinalIgnoreCase)
                            select r).SingleOrDefault();

                if (rule != null)
                {
                    rule.Attribute("name").Value = mappingRule.Name;
                    rule.Attribute("claimType").Value = mappingRule.ClaimType;
                    rule.Attribute("claimValue").Value = mappingRule.ClaimValue;

                    mappingRules.Save(this.filePath);
                }
            }
        }

        public void DeleteMappingRule(Guid id)
        {
            var mappingRules = this.LoadXml();

            if (mappingRules != null)
            {
                var rule = (from r in mappingRules.Descendants("mappingRule")
                            where r.Attribute("id").Value.Equals(id.ToString(), StringComparison.OrdinalIgnoreCase)
                            select r).SingleOrDefault();

                if (rule != null)
                {
                    rule.Remove();
                    mappingRules.Save(this.filePath);
                }
            }
        }

        private static IEnumerable<XElement> EnsureMemberGroupSection(IdentityProviderInfo identityProvider, MemberGroupInfo memberGroup, ref XDocument mappingRules, IEnumerable<XElement> identityProviders)
        {
            var memberGroups = identityProviders.Single(ip => ip.Attribute("id").Value.Equals(identityProvider.Id, StringComparison.OrdinalIgnoreCase)).Elements("memberGroup");
            if (!memberGroups.Any(mg => mg.Attribute("id").Value == memberGroup.Id.ToString()))
            {
                var xml = new XElement("memberGroup",
                    new XAttribute("id", memberGroup.Id),
                    new XAttribute("name", memberGroup.Name));

                if (memberGroups.Count() == 0)
                {
                    identityProviders.Single(ip => ip.Attribute("id").Value.Equals(identityProvider.Id, StringComparison.OrdinalIgnoreCase)).Add(xml);
                }
                else
                {
                    memberGroups.Last().AddAfterSelf(xml);
                }

                memberGroups = mappingRules.Element("mappingRules").Elements("identityProvider").Single(ip => ip.Attribute("id").Value.Equals(identityProvider.Id, StringComparison.OrdinalIgnoreCase)).Elements("memberGroup");
            }
            return memberGroups;
        }

        private static IEnumerable<XElement> EnsureIdentityProviderSection(IdentityProviderInfo identityProvider, ref XDocument mappingRules)
        {
            var identityProviders = mappingRules.Element("mappingRules").Elements("identityProvider");
            if (!identityProviders.Any(ip => ip.Attribute("id").Value.Equals(identityProvider.Id, StringComparison.OrdinalIgnoreCase)))
            {
                var xml = new XElement("identityProvider",
                    new XAttribute("id", identityProvider.Id),
                    new XAttribute("name", identityProvider.Name));

                if (identityProviders.Count() == 0)
                {
                    mappingRules.Element("mappingRules").Add(xml);
                }
                else
                {
                    identityProviders.Last().AddAfterSelf(xml);
                }

                identityProviders = mappingRules.Element("mappingRules").Elements("identityProvider");
            }

            return identityProviders;
        }

        private XDocument LoadXml()
        {
            if (File.Exists(this.filePath))
            {
                return XDocument.Load(this.filePath);
            }

            return null;
        }
    }
}